home *** CD-ROM | disk | FTP | other *** search
/ Total Network Tools 2002 / NextStepPublishing-TotalNetworkTools2002-Win95.iso / Archive / Misc Servers / Zope.exe / KJPARSER.PY < prev    next >
Encoding:
Text File  |  1999-11-03  |  45.8 KB  |  1,301 lines

  1. #
  2. # python for parser interpretation
  3. #  Copyright Aaron Robert Watters, 1994
  4. #
  5.  
  6. # BUGS:
  7. # Lexical error handling is not nice
  8. # Parse error handling is not nice
  9. #
  10. # Lex analysis may be slow for big grammars
  11. # Setting case sensitivity for keywords MUST happen BEFORE
  12. #   declaration of keywords.
  13.  
  14. import kjSet
  15. import string
  16. import regex
  17. import regsub
  18. import string
  19.  
  20. # set this flag for regression testing at each load
  21. RUNTESTS = 0
  22.  
  23. # set this flag to enable warning for default reductions
  24. WARNONDEFAULTS = 0
  25.  
  26. # some local constants
  27. TERMFLAG = -1 # FLAG FOR TERMINAL
  28. NOMATCHFLAG = -2 # FLAG FOR NO MATCH IN FSM
  29. MOVETOFLAG = -3 # FLAG FOR "SHIFT" IN SN FSM
  30. REDUCEFLAG = -4 # FLAG FOR REDUCTION IN FSM
  31. TRANSFLAG = -5 # FLAG FOR TRANSIENT STATE IN FSM
  32. KEYFLAG = -6 # FLAG FOR KEYWORD
  33. NONTERMFLAG = -7 # FLAG FOR NONTERMINAL
  34. TERMFLAG = -8 # FLAG FOR TERMINAL
  35. EOFFLAG = "*" # FLAG for End of file
  36.  
  37. # set this string to the Module name (filename)
  38. # used for dumping reconstructable objects
  39. THISMODULE = "kjParser"
  40.  
  41. # regular expression for matching whitespace
  42. WHITERE = "["+string.whitespace+"]+"
  43. WHITEREGEX = regex.compile(WHITERE)
  44.  
  45. # local errors
  46. LexTokenError = "LexTokenError" # may happen on bad string
  47. UnkTermError = "UnkTermError" # ditto
  48. BadPunctError= "BadPunctError" # if try to make whitespace a punct
  49. ParseInitError = "ParseInitError" # shouldn't happen?
  50. #EOFError # may happen on bad string
  51. FlowError = "FlowError" # shouldn't happen!!! (bug)
  52. #SyntaxError # may happen on bad string
  53. #TypeError
  54. ReductError = "ReductError" # shouldn't happen?
  55. NondetError = "NondetError" # shouldn't happen?
  56.  
  57. # the end of file is interpreted in the lexical stream as
  58. # a terminal...
  59. #  this should be appended to the lexical stream:
  60. ENDOFFILETOKEN = (TERMFLAG, EOFFLAG)
  61.  
  62. # in FSM use the following terminal to indicate eof
  63. ENDOFFILETERM = (ENDOFFILETOKEN, EOFFLAG)
  64.  
  65. # utility function for error diagnostics
  66. def DumpStringWindow(Str, Pos, Offset=15):
  67.     L = []
  68.     L.append("near ::")
  69.     start = Pos-Offset
  70.     end = Pos+Offset
  71.     if start<0: start = 0
  72.     if end>len(Str): end = len(Str)
  73.     L.append(`Str[start:Pos]`+"*"+`Str[Pos:end]`)
  74.     from string import join
  75.     return join(L, "\n")
  76.  
  77. # lexical dictionary class
  78. #   this data structure is used by lexical parser below.
  79. #
  80. #   basic operations:
  81. #      LD.punctuation(string)
  82. #         registers a string as a punctuation
  83. #           EG: LD.punctuation(":")
  84. #      Punctuations are treated as a special kind of keyword
  85. #      that is recognized even when not surrounded by whitespace.
  86. #      IE, "xend" will not be recognized as "x end", but "x;" will be
  87. #          recognized as "x ;" if "end" is a regular keyword but
  88. #          ";" is a punctuation.  Only single character punctuations
  89. #          are supported (now), ie, ":=" must be recognized as
  90. #          ":" "=" above the lexical level.
  91. #
  92. #      LD.comment(compiled_reg_expression)
  93. #         registers a comment pattern
  94. #           EG LD.comment(regex.compile("--.*\n"))
  95. #             asks to recognize ansi/sql comments like "-- correct?\n"
  96. #
  97. #      LD.keyword(keyword_string, canonicalstring)
  98. #         specifies a keyword string that should map to the canonicalstring
  99. #         when translated to the lexical stream.
  100. #           EG: LD.keyword("begin","BEGIN"); LD.keyword("BEGIN","BEGIN")
  101. #            will recognize upcase or downcase begins, not mixed case.
  102. #            (automatic upcasing is allowed below at parser level).
  103. #
  104. #      LD[compiled_reg_expression] = (TerminalFlag, Function) # assignment!
  105. #         specifies a regular expression that should be associated
  106. #         with the lexical terminal marker TerminalFlag
  107. #           EG: LD[regex.compile("[0-9]+")] = ("integer",string.atoi)
  108. #         the Function should be a function on one string argument
  109. #         that interprets the matching string as a value. if None is
  110. #         given, just the string itself will be used as the
  111. #         interpretation.  (a better choice above would be a function
  112. #         which "tries" atoi first and uses atol on overflow).
  113. #      NOTE: ambiguity among regular expressions will be decided
  114. #         arbitrarily (fix?).
  115. #
  116. #      LD[string] # retrieval!
  117. #         returns ((KEYFLAG, Keywordstring), Keywordstring)
  118. #          if the (entire) string matches a keyword or a 
  119. #          punctuation Keywordstring.
  120. #         otherwise returns ((TERMFLAG, Terminalname), value)
  121. #          if the (entire) string matches the regular expression for
  122. #          a terminal flaged by Terminalname; value is the interpreted
  123. #          value.  TerminalFlag better be something other than
  124. #          KEYFLAG!
  125. #         otherwise raises an error!
  126. #         comments not filtered here!
  127. #
  128. # the following additional functions are used for autodocumentation
  129. # in declaring rules, etcetera.
  130. #     begin = LD.keyword("begin")
  131. #        sets variable "begin" to (KEYFLAG, "BEGIN") if
  132. #        "begin" maps to keyword "BEGIN" in LD
  133. #     integer = LD.terminal("integer")
  134. #        sets variable integer to ("integer", Function)
  135. #        if  "integer" is a registered terminal Function is
  136. #        its associated interpretation function.
  137. #
  138. class LexDictionary:
  139.  
  140.   def __init__(self):
  141.     # commentpatterns is simply a list of compiled regular expressions
  142.     # that represent comments
  143.     self.commentpatterns = []
  144.     # commentstrings is used for debugging/dumping/reconstruction etc.
  145.     self.commentstrings = []
  146.     # punctuationlist is a string of punctuations
  147.     self.punctuationlist = ""
  148.     # keywordmap is a dictionary mapping recognized keyword strings
  149.     # and punctuations to their constant representations.
  150.     self.keywordmap = KeywordDict()
  151.     # regexprlist is a list of triples (regex,Flag,function) mapping
  152.     # regular expressions to their flag and interpreter function.
  153.     self.regexprlist = []
  154.  
  155.   def Dump(self):
  156.     print "comments = ", self.commentstrings
  157.     print "punctuations = ", self.punctuationlist
  158.     print "keywordmap ="
  159.     self.keywordmap.Dump()
  160.     print "regexprlist =", self.regexprlist
  161.  
  162.   def __getitem__(self,key):
  163.     # try to match string to a keyword
  164.     try:
  165.        return self.keywordmap[key]
  166.     except KeyError:
  167.        # try to match a regular expression
  168.        found = 0 # so far not found
  169.        length = len(key)
  170.        for triple in self.regexprlist:
  171.           (regexpr, Flag, Function) = triple
  172.           index = regexpr.match(key)
  173.           if index == length:
  174.              found = 1
  175.              # use the function to interpret the string, if given
  176.              if Function != None:
  177.                 value = Function(key)
  178.              else:
  179.                 value = key
  180.              # NONLOCAL RETURN
  181.              return (Flag, value)
  182.        #endfor
  183.        raise LexTokenError, "no match for string: " + `key`
  184.   #enddef __getitem__
  185.  
  186.   # LD.keyword("this") will make a new keyword "this" if not found
  187.   #
  188.   def keyword(self,str):
  189.     # upcase the string, if needed
  190.     if self.keywordmap.caseInsensitive:
  191.        str = string.upper(str)
  192.     if not self.keywordmap.has_key(str):
  193.        # redundancy for to avoid excess construction during parsing
  194.        token = (KEYFLAG,str)
  195.        self.keywordmap[str] = (token,str)
  196.     else:
  197.        (token, str2) = self.keywordmap[str]
  198.     return token
  199.  
  200.   # LD.terminal("this") will just look for "this"
  201.   # LD.terminal("this", RE, F) will register a new terminal
  202.   #   RE must be a compiled regular expression or string reg ex
  203.   #   F must be an interpretation function
  204.   #
  205.   def terminal(self, string, RegExpr=None, Function=None):
  206.     if RegExpr != None and Function != None:
  207.        if type(RegExpr) == type(""):
  208.           RegExpr = regex.compile(RegExpr)
  209.        self[ RegExpr ] = ( string, Function)
  210.     for triple in self.regexprlist:
  211.        (regexpr,token,Function) = triple
  212.        if token[1] == string:
  213.           # nonlocal exit
  214.           return token
  215.     #endfor
  216.     # error if no exit by now
  217.     raise UnkTermError, "no such terminal"
  218.  
  219.   def __setitem__(self,key,value):
  220.     if type(key) == type(''):
  221.        # if it's a string it must be a keyword
  222.        if self.keywordmap.caseInsensitive:
  223.           value = string.upper(value)
  224.           key = string.upper(key)
  225.        self.keywordmap[key] = ( (KEYFLAG, value), value)
  226.     else:
  227.        # otherwise it better be a compiled regular expression (not
  228.        #verified)
  229.        (Name, Function) = value
  230.        Flag = (TERMFLAG, Name)
  231.        regexpr = key
  232.        self.regexprlist = self.regexprlist + \
  233.                           [ (regexpr, Flag, Function) ]
  234.  
  235.   # register a regular expression as a comment
  236.   def comment(self, string):
  237.     # regexpr better be a uncompiled string regular expression! (not verified)
  238.     regexpr = regex.compile(string)
  239.     self.commentpatterns = self.commentpatterns + [ regexpr ]
  240.     self.commentstrings = self.commentstrings + [ string ]
  241.  
  242.   # register a string as a punctuation
  243.   def punctuation(self,Instring):
  244.     if type(Instring) != type("") or len(Instring)!=1:
  245.        raise BadPunctError, "punctuation must be string of length 1"
  246.     if Instring in string.whitespace:
  247.        raise BadPunctError, "punctuation may not be whitespace"
  248.     self.punctuationlist = self.punctuationlist + Instring
  249.     return self.keyword(Instring)
  250.  
  251.   # testing and altering case sensitivity behavior
  252.   def isCaseSensitive(self):
  253.     return not self.keywordmap.caseInsensitive
  254.  
  255.   # setting case sensitivity MUST happen before keyword
  256.   # declarations!
  257.   def SetCaseSensitivity(self, Boolean):
  258.     self.keywordmap.caseInsensitive = not Boolean
  259.  
  260.   # function to do same as __getitem__ above but looking _inside_ a string
  261.   # instead of at the whole string
  262.   # returns (token,skip)
  263.   # where token is one of
  264.   #  ((KEYFLAG,name),name) or ((TERMFLAG,termname),value)
  265.   # and skip is the length of substring of string that matches thetoken
  266.   def Token(self, String, StartPosition):
  267.      finished = 0 # dummy, exit should be nonlocal
  268.      totalOffset = 0
  269.      while not finished:
  270.         # flag EOF if past end of string?
  271.         if len(String) <= StartPosition:
  272.            return (ENDOFFILETERM, 0)
  273.         # skip whitespace
  274.         whitespacefound = 0
  275.         skip = WHITEREGEX.match(String, StartPosition)
  276.         if skip > 0:
  277.            StartPosition = StartPosition + skip
  278.            totalOffset = totalOffset + skip
  279.            whitespacefound = 1
  280.         # try to find comment, keyword, term in that order:
  281.         # looking for comment
  282.         commentfound = 0
  283.         for commentexpr in self.commentpatterns:
  284.            offset = commentexpr.match(String,StartPosition)
  285.            if offset != -1:
  286.               if offset<1:
  287.                  info = DumpStringWindow(String,StartPosition)
  288.                  raise LexTokenError, "zero length comment "+info
  289.               commentfound = 1
  290.               StartPosition = StartPosition + offset
  291.               totalOffset = totalOffset + offset
  292.         # looking for a keyword
  293.         keypair = self.keywordmap.hasPrefix(String,StartPosition,
  294.                       self.punctuationlist)
  295.         if keypair != 0:
  296.            return ( keypair[0], keypair[1] + totalOffset)
  297.         # looking for terminal
  298.         for (regexpr, Flag, Function) in self.regexprlist:
  299.            offset = regexpr.match(String,StartPosition)
  300.            if offset != -1:
  301.               matchstring = String[StartPosition : offset+StartPosition]
  302.               if Function != None:
  303.                  value = Function(matchstring)
  304.               else:
  305.                  value = matchstring
  306.               return ((Flag, value) , offset + totalOffset)
  307.         if not (commentfound or whitespacefound):
  308.            info = DumpStringWindow(String,StartPosition)
  309.            raise LexTokenError, "Lexical parse failure "+info
  310.      #endwhile
  311.   #enddef
  312. #endclass LexDictionary
  313.  
  314. # alternate, experimental implementation
  315.  
  316. class lexdictionary:
  317.  
  318.    def __init__(self):
  319.        self.skip = ""
  320.        self.commentstrings = []
  321.        self.punctuationlist = ""
  322.        self.keywordmap = KeywordDict()
  323.        self.termlist = [] # list of (term, regex, flag, interpret_fn)
  324.        self.uncompiled = 1 # only compile after full initialization.
  325.        self.laststring= self.lastindex= self.lastresult = None
  326.  
  327.    def Dump(self, *k):
  328.        raise "sorry", "not implemented"
  329.    __getitem__ = Dump
  330.  
  331.    def keyword(self, str):
  332.        kwm = self.keywordmap
  333.        if kwm.caseInsensitive:
  334.           str = string.upper(str)
  335.        try:
  336.           (token, str2) = kwm[str]
  337.        except:
  338.           token = (KEYFLAG, str)
  339.           self.keywordmap[str] = (token,str)
  340.        return token
  341.  
  342.    def terminal(self, str, regexstr=None, Function=None):
  343.        if regexstr is not None:
  344.           flag = (TERMFLAG, str)
  345.           self.termlist.append( (str, regexstr, flag, Function) )
  346.           return flag
  347.        else:
  348.           for (s,fl,fn) in self.termlist:
  349.               if fl[1]==str:
  350.                  return fl
  351.               else:
  352.                  raise UnkTermError, "no such terminal"
  353.  
  354.    __setitem__ = Dump
  355.  
  356.    def comment(self, str):
  357.        self.commentstrings.append(str)
  358.  
  359.    def punctuation(self, Instring):
  360.        if type(Instring) != type("") or len(Instring)!=1:
  361.          raise BadPunctError, "punctuation must be string of length 1"
  362.        if Instring in string.whitespace:
  363.          raise BadPunctError, "punctuation may not be whitespace"
  364.        self.punctuationlist = self.punctuationlist + Instring
  365.        return self.keyword(Instring)
  366.  
  367.    def SetCaseSensitivity(self, Boolean):
  368.        self.keywordmap.caseInsensitive = not Boolean
  369.  
  370.    def Token(self, String, StartPosition):
  371.        # shortcut for reductions.
  372.        if self.laststring is String and self.lastindex == StartPosition:
  373.           #print "lastresult", self.lastresult
  374.           return self.lastresult
  375.        self.lastindex = StartPosition
  376.        self.laststring = String
  377.        #print `String[StartPosition: StartPosition+60]`
  378.  
  379.        if self.uncompiled:
  380.           self.compile()
  381.           self.uncompiled = None
  382.        finished = 0
  383.        totalOffset = 0
  384.        skipprog = self.skipprog
  385.        keypairfn = self.keywordmap.hasPrefix
  386.        punctlist = self.punctuationlist
  387.        termregex = self.termregex
  388.        while not finished:
  389.           #print String[StartPosition:]
  390.           if len(String) <= StartPosition:
  391.              result = self.lastresult = (ENDOFFILETERM, 0)
  392.              return result
  393.           # skip ws and comments 
  394.           skip = skipprog.match(String, StartPosition)
  395.           if skip>0:
  396.              if skip==0:
  397.                 info = DumpStringWindow(String, StartPosition)
  398.                 raise LexTokenError, \
  399.                      "zero length whitespace or comment "+info
  400.              #print "skipping", `String[StartPosition: StartPosition+skip]`
  401.              StartPosition = StartPosition + skip
  402.              totalOffset = totalOffset + skip
  403.              continue
  404.           # look for keyword
  405.           keypair = keypairfn(String, StartPosition, punctlist)
  406.           if keypair!=0:
  407.              #print "keyword", keypair
  408.              result = self.lastresult = (keypair[0], keypair[1]+totalOffset)
  409.              return result
  410.           # look for terminal
  411.           offset = termregex.match(String, StartPosition)
  412.           if (offset>0):
  413.              g = termregex.group
  414.              for (term, regex, flag, fn) in self.termlist:
  415.                  test = g(term)
  416.                  if test:
  417.                     #print "terminal", test
  418.                     if fn is not None:
  419.                        value = fn(test)
  420.                     else:
  421.                        value = test
  422.                     result = self.lastresult = (
  423.                        (flag, value), offset + totalOffset)
  424.                     return result
  425.           # error if we get here
  426.           info = DumpStringWindow(String, StartPosition)
  427.           raise LexTokenError, "Lexical token not found "+info
  428.  
  429.    def isCaseSensitive(self):
  430.        return not self.keywordmap.caseInsensitive
  431.  
  432.    def compile(self):
  433.        from string import joinfields, whitespace
  434.        import regex
  435.        skipregexen = self.commentstrings + [WHITERE]
  436.        skipregex = "\(" + joinfields(skipregexen, "\)\|\(") + "\)"
  437.        #print skipregex; import sys; sys.exit(1)
  438.        self.skipprog = regex.compile(skipregex)
  439.        termregexen = []
  440.        termnames = []
  441.        for (term, rgex, flag, fn) in self.termlist:
  442.            fragment = "\(<%s>%s\)" % (term, rgex)
  443.            termregexen.append(fragment)
  444.            termnames.append(term)
  445.        termregex = joinfields(termregexen, "\|")
  446.        self.termregex = regex.symcomp(termregex)
  447.        self.termnames = termnames
  448.  
  449. LexDictionary = lexdictionary ##### test!
  450.  
  451. # a utility class: dictionary of prefixes
  452. #  should be generalized to allow upcasing of keyword matches
  453. class KeywordDict:
  454.  
  455.   def __init__(self, caseInsensitive = 0):
  456.      self.FirstcharDict = {}
  457.      self.KeyDict = {}
  458.      self.caseInsensitive = caseInsensitive
  459.  
  460.   def Dump(self):
  461.      if self.caseInsensitive:
  462.         print "  case insensitive"
  463.      else:
  464.         print "  case sensitive"
  465.      keys = self.KeyDict.keys()
  466.      print "  keyDict has ", len(keys), " elts"
  467.      for key in keys:
  468.         print "     ", key," maps to ",self.KeyDict[key]
  469.      firstchars = self.FirstcharDict.keys()
  470.      print "  firstcharDict has ", len(firstchars), " elts"
  471.      for char in firstchars:
  472.         print "     ", char," maps to ",self.FirstcharDict[char]
  473.  
  474.   # set item assumes value has correct case already, if case sensitive
  475.   def __setitem__(self, key, value):
  476.      if len(key)<1:
  477.         raise LexTokenError, "Keyword of length 0"
  478.      if self.caseInsensitive:
  479.         KEY = string.upper(key)
  480.      else:
  481.         KEY = key
  482.      firstchar = KEY[0:1]
  483.      if self.FirstcharDict.has_key(firstchar):
  484.         self.FirstcharDict[firstchar] = \
  485.           self.FirstcharDict[firstchar] + [(KEY, value)]
  486.      else:
  487.         self.FirstcharDict[firstchar] = [(KEY, value)]
  488.      self.KeyDict[KEY] = value
  489.  
  490.   # if String has a registered keyword at start position
  491.   #  return its canonical representation and offset, else 0
  492.   # keywords that are not punctuations should be 
  493.   # recognized only if followed
  494.   # by a punctuation or whitespace char
  495.   #
  496.   def hasPrefix(self,String,StartPosition,punctuationlist):
  497.      First = String[StartPosition:StartPosition+1]
  498.      fcd = self.FirstcharDict
  499.      caseins = self.caseInsensitive
  500.      if caseins:
  501.         First = string.upper(First)
  502.      if fcd.has_key(First):
  503.         Keylist = fcd[First]
  504.      else:
  505.         return 0
  506.      for (key,value) in Keylist:
  507.            offset = len(key)
  508.            EndPosition = StartPosition+offset
  509.            match = String[StartPosition : EndPosition]
  510.            if caseins:
  511.               match = string.upper(match)
  512.            if key == match:
  513.               if len(key)==1 and key in punctuationlist:
  514.                  # punctuations are recognized regardless of nextchar
  515.                  return (value,offset)
  516.               else:
  517.                  # nonpuncts must have punct or whitespace following
  518.                  #(uses punct as single char convention)
  519.                  if EndPosition == len(String):
  520.                     return (value, offset)
  521.                  else:
  522.                     nextchar = String[EndPosition]
  523.                     if nextchar in string.whitespace\
  524.                      or nextchar in punctuationlist:
  525.                        return (value, offset)
  526.      return 0 # if no exit inside for loop, fail
  527.  
  528.   def __getitem__(self,key):
  529.      if self.caseInsensitive:
  530.         key = string.upper(key)
  531.      return self.KeyDict[key]
  532.  
  533.   def has_key(self,key):
  534.      if self.caseInsensitive:
  535.         key = string.upper(key)
  536.      return self.KeyDict.has_key(key)
  537. #endclass KeywordDict:
  538.  
  539. # LexStringWalker walks through a string looking for
  540. # substrings recognized by a lexical dictionary
  541. #
  542. #  ERROR REPORTING NEEDS IMPROVEMENT
  543. class LexStringWalker:
  544.  
  545.   def __init__(self, String, LexDict):
  546.     self.Position = 0
  547.     self.NextPosition = 0
  548.     self.String = String
  549.     self.LexDict = LexDict
  550.     self.PastEOF = 0
  551.     self.Done = 0
  552.  
  553.   def DUMP(self):
  554.     return DumpStringWindow(self.String,self.Position)
  555.  
  556.   #reset not defined
  557.  
  558.   def more(self):
  559.     return not self.PastEOF
  560.  
  561.   def getmember(self):
  562.     (Token,skip) = self.LexDict.Token(self.String, self.Position)
  563.     self.NextPosition = self.Position + skip
  564.     if Token == ENDOFFILETERM:
  565.        self.PastEOF = 1
  566.     return Token
  567.  
  568.   def next(self):
  569.     if self.Done:
  570.        data = self.DUMP()
  571.        raise LexTokenError, "no next past end of file "+data
  572.     elif self.PastEOF:
  573.        self.Done=1
  574.     elif self.NextPosition > self.Position:
  575.        self.Position = self.NextPosition
  576.     else:
  577.        dummy = self.getmember()
  578.        if self.NextPosition <= self.Position:
  579.           data = self.DUMP()
  580.           raise LexTokenError, "Lexical walker not advancing "+data
  581.        self.Position = self.NextPosition
  582.  
  583. #endclass LexStringWalker
  584.  
  585. # the parse class:
  586. #   Based loosely on Aho+Ullman, Principles of Compiler Design, Ch.6.
  587. #    except that they don't describe how to handle boundary
  588. #    conditions, I made them up myself.
  589. #
  590. #   Note: This could be implemented using just functions; it's implemented
  591. #    as a class to facilitate diagnostics and debugging in case of
  592. #    failures of various sorts.
  593. #
  594. # a parse accepts 
  595. #   a rule list
  596. #
  597. #   a lexically analysed stream with methods
  598. #     stream.getmember()  returns the current token on the stream
  599. #     stream.next()  moves on to next token
  600. #     stream.more()     returns false if current token is the last token
  601. #
  602. #   and a FSM (finite state machine) with methods
  603. #     FSM.root_nonTerminal
  604. #       the nonterminal at which to start parsing
  605. #     FSM.initial_state
  606. #       the initial state to start at
  607. #     FSM.successful_final_state
  608. #       the final state to go to upon successful parse
  609. #     FSM.map(Current_State,Current_Token)
  610. #       returns either
  611. #          (TERMFLAG, 0)
  612. #             if Current_State is terminal (final or reduction).
  613. #          (NOMATCHFLAG, 0)
  614. #             if Current_State is nonterminal, but the Current_Token
  615. #             and Next_Token do not lead to a valid state in the FSM
  616. #          (MOVETOFLAG, Next_State)
  617. #             if Current_State is nonterminal and Current_Token,
  618. #             Next_token map to Next_State from Current_State.
  619. #          (REDUCEFLAG, Rulenum)
  620. #             if Current_State indicates a reduction at Current_Token
  621. #             for rule Rule number Rule
  622. #
  623. #    and a Stack with methods (replaced with dictionary)
  624. #          (init: {-1:0} )
  625. #       Stack.Top() returns top of stack (no pop)
  626. #          ( Stack[Stack[-1]] )
  627. #       Stack.Push(Object)
  628. #          ( Stack[-1]=Stack[-1]+1; Stack[Stack[-1]]=Object )
  629. #       Stack.MakeEmpty()
  630. #          ( Stack[-1]=0 )
  631. #       Stack.IsEmpty()
  632. #          ( Stack[-1] == 0 )
  633. #       Stack.Pop()
  634. #          ( Stack[-1] = Stack[-1]-1 )
  635. #       stack contents created by Parser will be of form (State,Value)
  636. #       where Value was inserted at FSM state State.
  637. #       Value of form either (KEYFLAG, Name)
  638. #                            (NontermName, reductionvalue)
  639. #                         or (TerminalName, value)
  640. #
  641. #    and an optional parameter Evaluate which if 0 indicates that
  642. #       rules should be evaluated, otherwise indicates that rules
  643. #       should just be reduced and the reduction structure should
  644. #       be used as the result of the rule
  645. #
  646. # rule objects must support methods
  647. #    Rule.reduce(Stack)
  648. #       pops off the elements corresponding to the body of the Rule
  649. #       from the stack and returns (NewStack,Red) where NewStack is
  650. #       the stack minus the body and Red is the result of evaluating the
  651. #       reduction function on this instance of the rule.
  652. #    Rule.Nonterm
  653. #       the nonterminal at the head of the rule
  654.  
  655. class ParserObj:
  656.  
  657.   # Evaluate determines whether rules should be evaluated
  658.   # after reductions.  Context is an argument passed to the
  659.   # list reduction function
  660.   #
  661.   def __init__(self, Rulelist, Stream, FSM, Stack, \
  662.                 Evaluate=1, \
  663.                 Context=None):
  664.     self.Rules = Rulelist
  665.     self.LexStream = Stream
  666.     self.FSM = FSM
  667.     self.Stack = Stack
  668.     self.Context = Context
  669.  
  670.     # start with empty stack, initial_state, no nonterminal
  671.     #self.Stack[-1] = 0#   self.Stack.MakeEmpty()
  672.     self.Stack[:] = []
  673.     self.State = FSM.initial_state
  674.     self.currentNonterm = None
  675.     self.Evaluate = Evaluate
  676.  
  677.   # DoOneReduction accepts tokens from the stream and pushes
  678.   # them onto the stack until a reduction state is reached.
  679.   #
  680.   # Resolve the reduction
  681.   #
  682.   def DoOneReduction(self):
  683.     current=self.State
  684.     FSM=self.FSM
  685.     Stack = self.Stack
  686.     Context = self.Context
  687.     Stream = self.LexStream
  688.     # the internal FSM.StateTokenMap dictionary is used directly here.
  689.     STMap = FSM.StateTokenMap
  690.     #if FSM.final_state(current):
  691.     #   raise ParseInitError, 'trying to reduce starting at final state'
  692.  
  693.     tokenVal = Stream.getmember()
  694.     #print "tokenVal", tokenVal
  695.     token = tokenVal[0]
  696.  
  697.     # push the token and traverse FSM until terminal state is reached
  698.     #(flag, nextThing) = FSM.map(current, token)
  699.     key = (current, token)
  700.     try:
  701.        (flag, nextThing) = STMap[key][0]
  702.     except KeyError:
  703.        flag = NOMATCHFLAG
  704.  
  705.     while flag == MOVETOFLAG:
  706.        nextState = nextThing
  707.        #print current, " shift ", token, 
  708.        # no sanity check, possible infinite loop
  709.  
  710.        # push current token and next state
  711.        ThingToPush = (nextState, tokenVal)
  712.        #print "pushing ", ThingToPush
  713.        #Stack[-1]=Stack[-1]+1; Stack[Stack[-1]]=ThingToPush
  714.        Stack.append(ThingToPush)
  715.        #Stack.Push( ThingToPush )
  716.  
  717.        # move to next token, next state
  718.        Stream.next()
  719.        # error if end of stream
  720.        if not Stream.more(): # optimized Stream.PastEOF (?)
  721.           data = Stream.DUMP()
  722.           raise EOFError, 'end of stream during parse '+data
  723.  
  724.        current = nextState
  725.        tokenVal = Stream.getmember()
  726.        token = tokenVal[0]
  727.        
  728.        #MAP = FSM.map(current,token)
  729.        key = (current, token)
  730.        try:
  731.           (flag, nextThing) = STMap[key][0]
  732.        except KeyError:
  733.           flag = NOMATCHFLAG
  734.  
  735.     # at end of while loop we should be at a reduction state
  736.  
  737.     if flag == REDUCEFLAG:
  738.        rulenum = nextThing
  739.        #print current, " reduce ", token, self.Rules[rulenum]
  740.        # normal case
  741.        # perform reduction
  742.        rule = self.Rules[rulenum]
  743.        Nonterm = rule.Nonterm
  744.        self.currentNonterm = Nonterm
  745.        (Stack, reduct) = rule.reduce( Stack , Context )
  746. #       self.Stack = Stack #not needed, unless stack rep changes
  747.        GotoState = self.GotoState(rule)
  748.        # push the Gotostate and result of rule reduction on stack
  749.        ThingToPush = (GotoState, (Nonterm, reduct) )
  750.        # push the result of the reduction and exit normally
  751.        #print "pushing ", ThingToPush
  752.        #Stack[-1]=Stack[-1]+1; Stack[Stack[-1]]=ThingToPush
  753.        Stack.append(ThingToPush)
  754.        #Stack.Push(ThingToPush)
  755.        self.State=GotoState
  756.        return 1  # normal successful completion
  757.  
  758.     # some error cases
  759.     elif flag == NOMATCHFLAG:
  760.        self.ParseError(current,tokenVal, "nomatch1")
  761.     #elif FSM.final_state(current):
  762.     #   raise BadFinalError, 'unexpected final state reached in reduction'
  763.     else:
  764.        data = Stream.DUMP()
  765.        s = """
  766.            flag = %s
  767.            map = %s """ % (flag, FSM.map(current,token))
  768.        data = data + s
  769.        raise FlowError, 'unexpected else '+data
  770.   #enddef DoOneReduction
  771.  
  772.   # compute the state to goto after a reduction is performed
  773.   # on a rule.
  774.   # Algorithm: determine the state at beginning of reduction
  775.   #  and the next state indicated by the head nonterminal of the rule.
  776.   #  special case: empty stack and root nonterminal > success.
  777.   #
  778.   def GotoState(self, rule):
  779.      FSM = self.FSM
  780.      Stack = self.Stack
  781.      Head = rule.Nonterm
  782.      if len(Stack)==0: #Stack[-1]==0: #Stack.IsEmpty():
  783.         BeforeState = FSM.initial_state
  784.      else:
  785.         BeforeState = Stack[-1][0] #Stack[Stack[-1]][0] #Stack.Top()[0]
  786.      # is this right? if the stack is empty and the Head
  787.      # is the root nonterm, then goto is final state
  788.      if len(Stack)==0 and Head == FSM.root_nonTerminal:#Stack.isEmpty()
  789.         Result = FSM.successful_final_state
  790.      else:
  791.         # consider eliminating the call to .map here? (efficiency)
  792.         (flag, Result) = FSM.map(BeforeState, Head)
  793.         if flag != MOVETOFLAG:
  794.            #FSM.DUMP()
  795.            self.ParseError(BeforeState, Head, "notmoveto")
  796.      return Result
  797.  
  798.   def ParseError( self, State, Token, *rest):
  799.     # make this parse error nicer (add diagnostic methods?)
  800.     L = [""]
  801.     L.append("*******************************")
  802.     L.append("current state = "+`State`)
  803.     L.append("expects: ")
  804.     expects = ""
  805.     for (flag,name) in self.FSM.Expects(State):
  806.        if flag in (TERMFLAG, KEYFLAG):
  807.           expects = expects + `name`+ ", "
  808.     L.append(expects)
  809.     L.append(`rest`)
  810.     L.append("current token = " + `Token`)
  811.     #print "Stack =", 
  812.     #self.StackDump(5)
  813.     #print
  814.     from string import join
  815.     data = self.LexStream.DUMP() + join(L, "\n")
  816.     raise SyntaxError, 'unexpected token sequence.' + data
  817.  
  818.   def StackDump(self, N):
  819.     Stack = self.Stack
  820.     Topkey = len(Stack)
  821.     if Topkey>N:
  822.        Start = Topkey - N
  823.     else:
  824.        Start = 1
  825.     for i in range(Start,Topkey+1):
  826.        print " :: ", Stack[i],
  827.  
  828.   # execute parsing until done:
  829.   def GO(self):
  830.     while self.State != self.FSM.successful_final_state:
  831.               #self.FSM.final_state(self.State):
  832.        self.DoOneReduction()
  833.     # should I check that stack has only one elt here?
  834.     # return result of last reduction
  835.     return self.Stack[-1][1] #self.Stack.Top()[1]
  836.  
  837. #endclass ParserObj
  838.  
  839. # function for declaring a variable to represent a nonterminal:
  840. # eg Program = nonterminal("program") 
  841. #  included for convenient autodocumentation
  842. #
  843. def nonterminal(string):
  844.    return (NONTERMFLAG, string)
  845.  
  846. # declaring a terminal WITHOUT INSTALLING IT IN A LexDict
  847. def termrep(string):
  848.    return (TERMFLAG, string)
  849.  
  850. # the rule class
  851. #  a rule is defined by a goal nonterminal marker of form
  852. #    (NONTERMFLAG, Name)
  853. #  and a list defining the body which must contain elts of form
  854. #    (KEYFLAG, Name) or (NONTERMFLAG, Name) of (TERMFLAG, Name)
  855. #  and a reduction function which takes a list of the same size
  856. #  as the BodyList (consisting of the results of the evaluations of
  857. #  the previous reductions)
  858. #  and returns an interpretation for the body
  859.  
  860. # the following function is used as a default reduction function
  861. # for rules
  862. def DefaultReductFun( RuleResultsList, Context ):
  863.    if WARNONDEFAULTS:
  864.       print "warn: default reduction."
  865.       print "   ", RuleResultsList
  866.    return RuleResultsList
  867.  
  868. class ParseRule:
  869.  
  870.   def __init__(self, goalNonTerm, BodyList, \
  871.          ReductFunction = DefaultReductFun):
  872.      #print BodyList
  873.      # check some of the arguments (very limited!)
  874.      if len(goalNonTerm) != 2 or goalNonTerm[0] != NONTERMFLAG:
  875.         raise TypeError, "goal of rule must be nonterminal"
  876.      for m in BodyList:
  877.         #print m
  878.         if len(m) != 2:
  879.            raise TypeError, "invalid body form for rule"
  880.      self.Nonterm = goalNonTerm
  881.      self.Body = BodyList
  882.      self.ReductFun = ReductFunction
  883.  
  884.   # for dumping/reconstruction: LOSES THE INTERPRETATION FUNCTION!
  885.   def __repr__(self):
  886.      return THISMODULE + ".ParseRule" + `self.components()`
  887.  
  888.   # marshal-able components of a rule
  889.   def components(self):
  890.      return (self.Nonterm, self.Body)
  891.  
  892.   # rule.reduce(Stack) pops of the stack elements corresponding
  893.   # to the body of the rule and prepares the appropriate reduction
  894.   # object for evaluation (or not) at higher levels
  895.   #
  896.   def reduce(self, Stack, Context=None):
  897.      #print "reducing", Stack
  898.      Blength = len(self.Body)
  899.      #print Blength, len(self.Body)
  900.      # pop off previous results from stack corresponding to body
  901.      BodyResults = [None] * Blength
  902.      #BodyNames = [None] * Blength # for debug
  903.      #print "popping: "
  904.      for i in range(1,Blength+1):
  905.         Bindex = Blength - i  # stack contents pop off in reverse order
  906.  
  907.         # get and destructure the rule body entry
  908.         RuleEntry = self.Body[Bindex]
  909.         ( REkind , REname ) = RuleEntry
  910.  
  911.         # get and destructure the stack entry
  912.         PoppedValue = Stack[-i] #Stack.Top()
  913.         #print PoppedValue,
  914.         #del Stack[-1]# = Stack[-1]-1 #Stack.Pop()
  915.         SETokVal = PoppedValue[1]
  916.         SEvalue = SETokVal[1]
  917.         SEname = SETokVal[0][1]
  918.  
  919.         # the names from rule and stack must match (?)
  920.         if SEname != REname:
  921.            print SEname, REname
  922.            print self
  923.            raise ReductError, " token names don't match"
  924.  
  925.         # store the values for the reduction
  926.         BodyResults[Bindex] = SEvalue
  927.         #BodyNames[Bindex] = SEname # debug
  928.      #endfor
  929.      del Stack[len(Stack)-Blength:]
  930.      #print "reduced", Stack
  931.      #print
  932.      # evaluate the reduction, in context
  933.      reduct = self.ReductFun(BodyResults, Context)
  934.      if WARNONDEFAULTS and self.ReductFun is DefaultReductFun:
  935.         # should check whether name is defined before this...
  936.         print "  default used on ", self.Name
  937.      #Reduction( self.ReductFun, BodyResults, BodyNames )
  938.      return (Stack, reduct)
  939.   #enddef ParseRule.reduce
  940.  
  941. #endclass ParseRule
  942.  
  943. # for debugging: look through a rule list
  944. #  and print names of rules that have default binding
  945. #
  946. def PrintDefaultBindings(rulelist):
  947.     for r in rulelist:
  948.         if r.ReductFun is DefaultReductFun:
  949.            print r.Name
  950.  
  951. # the FSM class
  952. #
  953. class FSMachine:
  954.  
  955.   def __init__(self, rootNonTerm):
  956.  
  957.     # start and success state conventions
  958.     startState=1
  959.     successState=0
  960.  
  961.     self.root_nonTerminal = rootNonTerm
  962.     self.initial_state = startState
  963.     self.successful_final_state = successState
  964.  
  965.     # the list of states of the FSM, implemented as a dictionary
  966.     #  entries are identified by their index
  967.     #  content is 
  968.     #  a list whose first elt is either TRANSFLAG, or TERMFLAG
  969.     #  other list elts may be added by other layers (parse generator)
  970.     #  indicating the kind of the state.
  971.     self.States = {}
  972.  
  973.     # allocate start and success states
  974.     self.States[startState]=[TRANSFLAG]
  975.     self.States[successState]=[TERMFLAG]
  976.  
  977.     # the most recently allocated state
  978.     self.maxState= startState
  979.  
  980.     # the map of current token+state number to next state
  981.     #with entries of form (tokenname,state):nextstate_sequence
  982.     #
  983.     self.StateTokenMap = {}
  984.   #enddef FSM()
  985.  
  986.   # ForbiddenMark is for filtering out maps to an error state
  987.   def DUMP(self, DumpMapData=1, DumpStateData=1, ForbiddenMark={}):
  988.     print "root nonterminal is ", self.root_nonTerminal
  989.     print "start at ", self.initial_state
  990.     print "end at ", self.successful_final_state
  991.     print "number of states: ", self.maxState
  992.     if DumpStateData:
  993.        print
  994.        for State in range(0,self.maxState+1):
  995.           Data = self.States[State]
  996.           print State, ": ", Data
  997.     if DumpMapData:
  998.        print
  999.        for key in self.StateTokenMap.keys():
  1000.           map = self.StateTokenMap[key]
  1001.           if map[0][0] == MOVETOFLAG:
  1002.              ToStateData = self.States[map[0][1]]
  1003.              if len(ToStateData) < 2:
  1004.                 Mark = None
  1005.              else:
  1006.                 Mark = ToStateData[1]
  1007.              if Mark != ForbiddenMark:
  1008.                 print key, " > ", map, " = ", ToStateData
  1009.           else:
  1010.              print key, " > reduction to rule number ", map[0][1]
  1011.  
  1012.   # what tokens does a state expect?
  1013.   def Expects(self, State):
  1014.     keys = self.StateTokenMap.keys()
  1015.     Tokens = kjSet.NewSet( [] )
  1016.     for (state1,token) in keys:
  1017.        if State == state1:
  1018.           kjSet.addMember(token,Tokens)
  1019.     return kjSet.get_elts(Tokens)
  1020.  
  1021.   # "allocate" a new state of specified kind
  1022.   #   kind must either be TRANSFLAG, TERMFLAG or a rule object
  1023.   # returns the number of the new state
  1024.   def NewState(self, kind, AdditionalInfo = []):
  1025.     if not kind in (TRANSFLAG,TERMFLAG,REDUCEFLAG):
  1026.        raise TypeError, "unknown state kind"
  1027.     available = self.maxState+1
  1028.  
  1029.     self.States[available] = [kind] + AdditionalInfo
  1030.     self.maxState = available
  1031.     return available
  1032.  
  1033.   # Install a reduction transition in the FSM:
  1034.   # a reduction is represented by mapping to a rule index
  1035.   # no nondeterminism is allowed.
  1036.   def SetReduction(self, fromState, TokenRep, Rulenum):
  1037.     key = (fromState, TokenRep)
  1038.     if not self.StateTokenMap.has_key(key):
  1039.        self.StateTokenMap[ key ] = ((REDUCEFLAG, Rulenum),)
  1040.     else:
  1041.        raise ReductError, "attempt to set ambiguous reduction"
  1042.  
  1043.   # Install a "shift" or "goto transition in the FSM:
  1044.   # supports nondeterminism by storing a sequence of possible transitions
  1045.   #
  1046.   def SetMap(self, fromState, TokenRep, toState): 
  1047.     key = (fromState, TokenRep)
  1048.     if self.StateTokenMap.has_key(key):
  1049.        Old = self.StateTokenMap[key]
  1050.        if Old[0][0] != MOVETOFLAG:
  1051.           # if the old value was not an integer, not a "normal state":
  1052.           # complain:
  1053.           raise NondetError, \
  1054.             "attempt to make inappropriate transition ambiguous"
  1055.        self.StateTokenMap[ key ] = Old + ((MOVETOFLAG,toState),)
  1056.     else:
  1057.        self.StateTokenMap[ key ] = ((MOVETOFLAG,toState),)
  1058.  
  1059.   # Find the action indicated by fsm on 
  1060.   #  (current_state, current_token) input.
  1061.   #
  1062.   # note: in the event of nondeterministic choice this chooses
  1063.   #   the first possibility listed.
  1064.   # ParseObj.DoOneReduction() currently uses the internal structure
  1065.   #  of StateTokenMap directly, rather than using this function.
  1066.   #
  1067.   def map(self, current_state, current_token):
  1068.     StateEntry = self.States[current_state][0]
  1069.     if StateEntry == TERMFLAG:
  1070.        return (TERMFLAG, 0)
  1071.     elif StateEntry == TRANSFLAG:
  1072.        # try to find a transition for this token and state
  1073.        key = (current_state, current_token)
  1074.        try:
  1075.           TMap = self.StateTokenMap[key]
  1076.           #print "TMap ", TMap
  1077.           #print "key ", key
  1078.           #print
  1079.           return TMap[0]
  1080.        except KeyError:
  1081.           return (NOMATCHFLAG, 0)
  1082.     else:
  1083.        raise FlowError, "unexpected else (2)"
  1084.   #enddef map
  1085.  
  1086. #endclass FSMachine
  1087.  
  1088. # the grammar class:
  1089. #   a grammar consists of 
  1090. #     - a LexDict lexical dictionary;
  1091. #     - a deterministic FSMachine;
  1092. #     - a Rulelist
  1093. #   and optionally a dictionary that maps Rulenames
  1094. #   to Rulelist indices (used for dumping and externally)
  1095. #   
  1096. class Grammar:
  1097.  
  1098.    def __init__(self, LexD, DFA, RuleL, RuleNameDict = None):
  1099.       # for auto initialization set LexD,DFA,RuleL to None
  1100.       if LexD == None and DFA == None and RuleL == None:
  1101.          self.LexD = LexDictionary()
  1102.          # use a dummy root nonterminal -- must fix elsewhere!
  1103.          self.DFA = FSMachine("ERROR")
  1104.          self.RuleL = []
  1105.       else:
  1106.          self.LexD = LexD
  1107.          self.DFA = DFA
  1108.          self.RuleL = RuleL
  1109.       if RuleNameDict != None:
  1110.          self.AddNameDict(RuleNameDict)
  1111.       self.CleanUp()
  1112.    #enddef __init__
  1113.  
  1114.    # look for default bindings
  1115.    def PrintDefaults(self):
  1116.        print "Default bindings on:"
  1117.        PrintDefaultBindings(self.RuleL)
  1118.  
  1119.    # setting case sensitivity: must happen before keyword installation
  1120.    # in LexD.
  1121.    def SetCaseSensitivity( self, Boolean ):
  1122.       self.LexD.SetCaseSensitivity( Boolean )
  1123.  
  1124.    # this may be silly, but to save some space in construction
  1125.    # a token dictionary may be used that facilitates sharing of
  1126.    # token representations.  This method either initializes
  1127.    # the dictionary or disposes of it if it exists
  1128.    def CleanUp(self):
  1129.       self.IndexToToken = {}
  1130.       # this dictionary is used by automatically
  1131.       # generated grammars to determine whether
  1132.       # a string represents a nonterminal
  1133.       self.NonTermDict = {}
  1134.       # similarly for terminals
  1135.       self.TermDict = {}
  1136.       # this string may be used to keep a printable
  1137.       # representation of the rules of the grammar
  1138.       # (usually in automatic grammar generation
  1139.       self.RuleString = ""
  1140.  
  1141.    # to associate a token to an integer use
  1142.    # self.IndexToToken[int] = tokenrep
  1143.  
  1144.    # this method associates rules to names using a
  1145.    # RuleNameDict dictionary which maps names to rule indices.
  1146.    # after invocation
  1147.    #   self.RuleNameToIndex[ name ] gives the index
  1148.    #     in self.RuleL for the rule associated with name, and
  1149.    #   self.RuleL[index].Name gives the name associated
  1150.    #     with the rule self.RuleL[index]
  1151.    #
  1152.    def AddNameDict(self, RuleNameDict):
  1153.      self.RuleNameToIndex = RuleNameDict
  1154.      # add a Name attribute to the rules of the rule list
  1155.      for ruleName in RuleNameDict.keys():
  1156.         index = RuleNameDict[ ruleName ]
  1157.         self.RuleL[ index ].Name = ruleName
  1158.  
  1159.    # parse a string using the grammar, return result and context
  1160.    def DoParse( self, String, Context = None, DoReductions = 1 ):
  1161.      # construct the ParserObj
  1162.      Stream = LexStringWalker( String, self.LexD )
  1163.      Stack = [] # {-1:0} #Walkers.SimpleStack()
  1164.      ParseOb = ParserObj( self.RuleL, Stream, self.DFA, Stack, \
  1165.                          DoReductions, Context )
  1166.      # do the parse
  1167.      ParseResult = ParseOb.GO()
  1168.      # return final result of reduction and the context
  1169.      return (ParseResult[1], Context)
  1170.    #enddef DoParse
  1171.  
  1172.    # parse a string using the grammar, but only return
  1173.    # the result of the last reduction, without the context
  1174.    def DoParse1( self, String, Context=None, DoReductions=1 ):
  1175.        return self.DoParse(String, Context, DoReductions)[0]
  1176.  
  1177.    # if the Name dictionary has been initialized
  1178.    # this method will (re)bind a reduction function to
  1179.    # a rule associated with Rulename    
  1180.    #
  1181.    def Bind( self, Rulename, NewFunction ):
  1182.      ruleindex = self.RuleNameToIndex[ Rulename ]
  1183.      rule = self.RuleL[ ruleindex ]
  1184.      rule.ReductFun = NewFunction
  1185.    #enddef Bind
  1186.  
  1187.    # bind a terminal to a regular expression and interp function
  1188.    # in the lexical dictionary (convenience)
  1189.    def Addterm( self, termname, regexpstr, funct ):
  1190.      self.TermDict[ termname ] =\
  1191.          self.LexD.terminal( termname, regexpstr, funct )
  1192. #endclass Grammar
  1193.  
  1194. # function to create a "null grammar"
  1195. def NullGrammar():
  1196.    return Grammar(None,None,None,{})
  1197.  
  1198. # unmarshalling a marshalled grammar created by
  1199. #   buildmodule.CGrammar.MarshalDump(Tofile)
  1200. #   tightly coupled with buildmodule code...
  1201. # file should be open and "pointing to" the marshalled rep.
  1202. #
  1203. # warning: doesn't bind semantics!
  1204. #
  1205. def UnMarshalGram(file):
  1206.     Grammar = NullGrammar()
  1207.     UnMarshal = UnMarshaller(file, Grammar)
  1208.     UnMarshal.MakeLex()
  1209.     UnMarshal.MakeRules()
  1210.     UnMarshal.MakeTransitions()
  1211.     UnMarshal.Cleanup()
  1212.     return UnMarshal.Gram
  1213.  
  1214. # unmarshalling object for unmarshalling grammar from a file
  1215. #
  1216. class UnMarshaller:
  1217.  
  1218.    def __init__(self, file, Grammar):
  1219.       import marshal
  1220.       self.Gram = Grammar
  1221.       BigList = marshal.load(file)
  1222.       if type(BigList) != type([]):
  1223.          raise FlowError, "bad type for unmarshalled list"
  1224.       if len(BigList) != 9:
  1225.          raise FlowError, "unmarshalled list of wrong size"
  1226.       self.tokens = BigList[0]
  1227.       self.punct = BigList[1]
  1228.       self.comments = BigList[2]
  1229.       self.RuleTups = BigList[3]
  1230.       self.MaxStates = BigList[4]
  1231.       self.reducts = BigList[5]
  1232.       self.moveTos = BigList[6]
  1233.       self.Root = BigList[7]
  1234.       self.CaseSensitivity = BigList[8]
  1235.       
  1236.       Grammar.SetCaseSensitivity( self.CaseSensitivity )
  1237.  
  1238.    def MakeLex(self):
  1239.       Grammar=self.Gram
  1240.       LexD = Grammar.LexD
  1241.       # punctuations
  1242.       LexD.punctuationlist = self.punct
  1243.       # comments
  1244.       for commentregex in self.comments:
  1245.           LexD.comment(commentregex)
  1246.       #LexD.commentstring = self.comments
  1247.       # keywords, terminals, nonterms
  1248.       #   rewrite the tokens list for sharing and extra safety
  1249.       LexTokens = {}
  1250.       tokens = self.tokens
  1251.       for tokenindex in range(len(tokens)):
  1252.           (kind,name) = tokens[tokenindex]
  1253.           if kind == KEYFLAG:
  1254.              tokens[tokenindex] = LexD.keyword(name)
  1255.           elif not kind in [TERMFLAG, NONTERMFLAG]:
  1256.              raise FlowError, "unknown token type"
  1257.       # not needed
  1258.       self.tokens = tokens
  1259.  
  1260.    def MakeRules(self):
  1261.       Grammar = self.Gram
  1262.       Grammar.DFA.root_nonTerminal = self.Root
  1263.       NameIndex = Grammar.RuleNameToIndex
  1264.       RuleTuples = self.RuleTups
  1265.       nRules = len(RuleTuples)
  1266.       RuleList = [None] * nRules
  1267.       for index in range(nRules):
  1268.          (Name, Components) = RuleTuples[index]
  1269.          rule = apply(ParseRule, Components)
  1270.          rule.Name = Name
  1271.          RuleList[index] = rule
  1272.          NameIndex[Name] = index
  1273.       Grammar.RuleL = RuleList
  1274.  
  1275.    def MakeTransitions(self):
  1276.       Grammar = self.Gram
  1277.       DFA = Grammar.DFA
  1278.       StateTokenMap = DFA.StateTokenMap
  1279.       tokens = self.tokens
  1280.       # record the state number
  1281.       DFA.maxState = self.MaxStates
  1282.       # this is historical, unfortunately...  CLEAN IT UP SOMEDAY!
  1283.       # THE DFA.States DICT IS NOT NEEDED (?) (here)
  1284.       for state in range(1, self.MaxStates+1):
  1285.          DFA.States[state] = [TRANSFLAG]
  1286.       # record the reductions
  1287.       for (fromState, TokenIndex, rulenum) in self.reducts:
  1288.          DFA.SetReduction(fromState, tokens[TokenIndex], rulenum)
  1289.       # record the transitions
  1290.       for (fromState, TokenIndex, ToState) in self.moveTos:
  1291.          DFA.SetMap(fromState, tokens[TokenIndex], ToState)
  1292.  
  1293.    def Cleanup(self):
  1294.       Grammar = self.Gram
  1295.       Grammar.CleanUp()
  1296.  
  1297. ################# FOLLOWING CODE IS FOR REGRESSION TESTING ONLY
  1298. ################# DELETE IT IF YOU WANT/NEED
  1299. #### All tests for this module deleted, since
  1300. #### ParseBuild module tests are sufficient.  
  1301.